// State functions for loading from and saving to cache.
#define PrynInternal
#define PrynImplementation
#include <pryn.h>

#if _WIN32
#define PrynStateCacheName "/prynCacheWindows.dat"
#endif

#define PrynCacheHeader "Pryn Cache File\r\n"
#define PrynCacheRevision 3
/*

Structure of a cache file:

A cache file routinely uses a dynamic integer type, marked uint. To decode, you need an interim value that's initially zero. Read a byte. Multiply the value by 128 and add the byte modulus 128 to it. If the byte is greater than 128, then continue; otherwise stop.

An Index is a uint which indexes another list. If the value is 0, then it is none of the entries. 1 is the first, 2 is the second, and so on. Many indexes may not be zero.

This is the header of the cache file:

	"Pryn Cache File" 0x0D 0x0A 0x00
		This is used to identify the file, and to produce an explicable result if it's printed in most operating systems.
	uint revision - Revision code for the file format. The current revision is 3.
	uint libraryCount - Number of libraries in the cache.
	uint mediatorCount - Number of mediators in the state.
	uint typeCount - Number of types in the state.
	uint factoryCount - Number of factories in the state.
	(v2) uint pathCount - Number of paths to load.
	[libraries] - List of libraries in the cache. Structure follows.
	[mediators] - List of mediators in the cache.
	[types] - List of types in the cache.
	[factories] - List of factories in the cache.
	(v2) [paths] - List of paths to load, each a PrynString.

The library structure has this format:

	PrynString filename - The identifying filename for the library. This is somewhat platform-dependent.
	(v3) PrynTime modified - Modification time.
	PrynCommonResource commonResource - Common resource data.

Each mediator has this format:

	PrynString id - Unique identifier of the mediator.
	Index library - Index of the library of this mediator; may not be 0.
	PrynCommonResource commonResource - Common resource data.

Each type has this format:

	PrynString id - Unique identifier of the type.
	Index library - Index of the library of this type; may not be zero.
	Index mediator - Index of the mediator of this type; may be zero.
	PrynCommonResource commonResource - Common resource data.

Each factory has this format:

	PrynString id - Unique identifier of the factory.
	Index library - Index of the library of this factory; may not be zero.
	Index mediator - Index of the mediator of this factory; may be zero.
	PrynCommonResource commonResource - Common resource data.

*/

#define loadData(POINTER, LENGTH) prynCall (prynStreamReadExact (stream, POINTER, LENGTH, 0))
#define loadUInt(VALUE) prynCall (prynStreamReadDynamicUInt (stream, &(VALUE)))
#define loadString(VALUE) prynCall (prynStreamReadDynamicString (stream, &(VALUE)))

#define loadIndex(TYPE, VALUE, LIST) \
	do { \
		size_t INDEX; \
		loadUInt (INDEX); \
		if (!INDEX) VALUE = 0; \
		else for (VALUE = LIST.first; VALUE && -- INDEX; ); \
	} while (0)

#define loadCommonResourceString(STRING, CHANGED) \
	do { \
		size_t length; \
		loadUInt (length); \
		CHANGED = !!(length & 1); \
		length >>= 1; \
		prynCall (prynStringAllocate (&STRING, state, length)); \
		prynCall (prynStreamReadExact (stream, (STRING).pointer, (STRING).length, 0)); \
	} while (0)

#define loadCommonResource(VALUE) \
	do { \
		PrynCommonResource *common = &(VALUE)->pCommonResource; \
		loadCommonResourceString (common->pName, common->pNameChanged); \
		loadCommonResourceString (common->pDescription, common->pDescriptionChanged); \
	} while (0)

#define loadIndexLibrary(VALUE) loadIndex (Library, VALUE, state->pLibraries)
#define loadIndexMediator(VALUE) loadIndex (Mediator, VALUE, state->pMediators)
#define loadIndexType(VALUE) loadIndex (Type, VALUE, state->pTypes)

#define saveData(POINTER, LENGTH) prynCall (prynStreamWriteExact (stream, POINTER, LENGTH, 0))
#define saveUInt(VALUE) prynCall (prynStreamWriteDynamicUInt (stream, (VALUE))) 
#define saveString(VALUE) prynCall (prynStreamWriteDynamicString (stream, &(VALUE)))

#define saveIndex(TYPE, VALUE, LIST) \
	do { \
		Pryn##TYPE *COUNTER = (VALUE); \
		size_t INDEX = 0; \
		\
		for ( ; COUNTER; COUNTER = COUNTER->pPrevious, INDEX ++); \
		saveUInt (INDEX); \
	} while (0)

#define saveCommonResource(VALUE) \
	do { \
		PrynCommonResource *common = &(VALUE)->pCommonResource; \
		saveUInt ((common->pNameChanged ? 1 : 0) | (common->pName.length * 2)); \
		saveData (common->pName.pointer, common->pName.length); \
		saveUInt ((common->pDescriptionChanged ? 1 : 0) | (common->pDescription.length * 2)); \
		saveData (common->pDescription.pointer, common->pDescription.length); \
	} while (0)

#define saveIndexLibrary(VALUE) saveIndex (Library, VALUE, state->pLibraries)
#define saveIndexMediator(VALUE) saveIndex (Mediator, VALUE, state->pMediators)
#define saveIndexType(VALUE) saveIndex (Type, VALUE, state->pTypes)

PrynExport (PrynResult, prynStreamReadDynamicString, (PrynStream *stream, PrynString *value))
{
	PrynStartFunction ();
	PrynString null = {0, 0};
	size_t length;

	if (value) *value = null;
	if (stream) state = stream->pState;
	prynAssert (stream, NullArgument);
	if (!value) value = &null;
	prynCall (prynStreamReadDynamicUInt (stream, &length));
	prynCall (prynStringAllocate (value, state, length));
	prynCall (prynStreamReadExact (stream, value->pointer, length, 0));
	if (value == &null) prynStringFree (value, state);
	
	PrynEndFunctionAfterFailure ({
		if (value && stream)
			prynStringFree (value, state);
	});
}

PrynExport (PrynResult, prynStreamReadDynamicUInt, (PrynStream *stream, unsigned int *output))
{
	PrynStartFunction ();
	unsigned int value = 0;
	unsigned char code;

	if (output) *output = 0;
	if (stream) state = stream->pState;
	prynAssert (stream, NullArgument);
	
	do
	{
		prynCall (prynStreamReadExact (stream, &code, 1, 0));
		value = (value << 7) | (code & 127);
	}
	while (code & 128);

	if (output) *output = value;
	PrynEndFunction ();
}

PrynExport (PrynResult, prynStreamWriteDynamicString, (PrynStream *stream, const PrynString *value))
{
	PrynStartFunction ();
	prynAssert (stream && value, NullArgument);
	prynCall (prynStreamWriteDynamicUInt (stream, value->length));
	prynCall (prynStreamWriteExact (stream, value->pointer, value->length, 0));
	PrynEndFunction ();
}

PrynExport (PrynResult, prynStreamWriteDynamicUInt, (PrynStream *stream, unsigned int value))
{
	PrynStartFunction ();
	unsigned char buffer [5];
	int shift = 0, written = 0;

	prynAssert (stream, NullArgument);
	state = stream->pState;

	for (shift = 0; (value >> shift) > 127; shift += 7);
	for ( ; shift >= 0; shift -= 7)
		buffer [written ++] = ((value >> shift) & 127)  | (shift ? 128 : 0);
	prynReturnCall (prynStreamWriteExact (stream, buffer, written, 0));

	PrynEndFunction ();
}

static PrynResult CacheFilename (PrynState *state, const PrynString *pathPointer, PrynString *filename)
{
	PrynStartFunctionHasState ();
	PrynString path;

	prynAssert (state && filename, NullArgument);

	// Figure out what path to use - the argument, the state, or the default.
	if (pathPointer && pathPointer->length)
		path = *pathPointer;
	else if (state->pCachePath.pointer)
		path = state->pCachePath;
	else
		prynCall (prynStateDefaultCachePath (state, &path));

	// Remove trailing slash if any.
	if (path.length && path.pointer [path.length - 1] == '/' || path.pointer [path.length - 1] == '\\')
		path.length --;

	// Duplicate the path into the state's cache path.
	if (path.pointer != state->pCachePath.pointer)
	{
		prynCall (prynStringFree (&state->pCachePath, state));
		prynCall (prynStringDuplicate (&state->pCachePath, state, &path));
	}

	prynCall (prynStringLiteral (filename, PrynStateCacheName));
	prynCall (prynStringJoin (filename, state, &path, filename));

	PrynEndFunction ();
}

PrynExport (PrynResult, prynStateLoadCache, (PrynState *state, const PrynString *path))
{
	PrynStartFunctionHasState ();
	PrynString filename = {0, 0}, id;
	PrynStream *stream = 0;
	PrynLibrary *library;
	PrynMediator *mediator;
	PrynFactory *factory;
	PrynType *type;
	unsigned int libraryCount, mediatorCount, factoryCount, typeCount, pathCount = 0, index;
	char magic [sizeof (PrynCacheHeader)];
	unsigned int revision;

	prynCall (CacheFilename (state, path, &filename));

	if (prynStreamOpenFile (&stream, state, &filename, true, false) < 0)
		prynReturnDone ();

	prynCall (prynStreamReadExact (stream, magic, sizeof (PrynCacheHeader), 0));
	prynAssert (PrynIsTrue (prynMemoryEqual (magic, PrynCacheHeader, sizeof (PrynCacheHeader))), BadArgument);

	loadUInt (revision);
	prynAssert (revision != 0 && revision <= PrynCacheRevision, BadArgument);

	loadUInt (libraryCount);
	loadUInt (mediatorCount);
	loadUInt (typeCount);
	loadUInt (factoryCount);
	if (revision >= 2) loadUInt (pathCount);
	
	for (index = 0; index < libraryCount; index ++)
	{
		loadString (id);
		prynCall (prynLibraryCreate (&library, state, &id));
		if (revision >= 3)
		{
			loadUInt (library->pModified.year);
			loadUInt (library->pModified.remainder);
		}
		loadCommonResource (library);
	}

	for (index = 0; index < mediatorCount; index ++)
	{
		loadString (id);
		loadIndexLibrary (library);
		prynCall (prynMediatorCreate (&mediator, library, &id));
		loadCommonResource (mediator);
	}

	for (index = 0; index < typeCount; index ++)
	{
		loadString (id);
		loadIndexLibrary (library);
		loadIndexMediator (mediator);
		prynCall (prynTypeCreate (&type, library, &id));
		type->pMediator = mediator;
		loadCommonResource (type);
	}

	for (index = 0; index < factoryCount; index ++)
	{
		loadString (id);
		loadIndexLibrary (library);
		loadIndexMediator (mediator);
		prynCall (prynFactoryCreate (&factory, library, &id));
		factory->pMediator = mediator;
		loadCommonResource (factory);
	}
	
	for (index = 0; index < pathCount; index ++)
	{
		loadString (id);
		result = prynStateAddLibraryPath (state, &id);
		prynPrintFormat (state, "loading: '%r'\n", id);
		prynStringFree (&id, state);
		prynCall (result);
	}

	PrynEndFunctionAfterCommon ({
		prynStringFree (&filename, state);
		prynStreamClose (stream);
	});
}

PrynExport (PrynResult, prynStateSaveCache, (PrynState *state, const PrynString *path))
{
	PrynStartFunctionHasState ();
	PrynString filename = {0, 0};
	PrynStream *stream = 0;
	PrynLibrary *library;
	PrynMediator *mediator;
	PrynFactory *factory;
	PrynType *type;
	unsigned int revision = PrynCacheRevision;
	size_t libraryCount, mediatorCount, factoryCount, typeCount, pathCount = 0, index;

	prynAssert (state, NullArgument);
	prynCall (CacheFilename (state, path, &filename));

	prynCall (prynStreamOpenFile (&stream, state, &filename, false, true));
	prynCall (prynStreamWriteExact (stream, PrynCacheHeader, sizeof (PrynCacheHeader), 0));
	saveUInt (revision);

	prynDListCountP (&state->pLibraries, library, libraryCount);
	prynDListCountP (&state->pMediators, mediator, mediatorCount);
	prynDListCountP (&state->pFactories, factory, factoryCount);
	prynDListCountP (&state->pTypes, type, typeCount);
	saveUInt (libraryCount);
	saveUInt (mediatorCount);
	saveUInt (typeCount);
	saveUInt (factoryCount);

	if (revision >= 2)
	{
		pathCount = state->pPathCount;
		saveUInt (pathCount);
	}

	for (library = state->pLibraries.first; library; library = library->pNext)
	{
		saveString (library->pFilename);
		if (revision >= 3)
		{
			saveUInt (library->pModified.year);
			saveUInt (library->pModified.remainder);
		}
		saveCommonResource (library);
	}

	for (mediator = state->pMediators.first; mediator; mediator = mediator->pNext)
	{
		saveString (mediator->pId);
		saveIndexLibrary (mediator->pLibrary);
		saveCommonResource (mediator);
	}

	for (type = state->pTypes.first; type; type = type->pNext)
	{
		saveString (type->pId);
		saveIndexLibrary (type->pLibrary);
		saveIndexMediator (type->pMediator);
		saveCommonResource (type);
	}

	for (factory = state->pFactories.first; factory; factory = factory->pNext)
	{
		saveString (factory->pId);
		saveIndexLibrary (factory->pLibrary);
		saveIndexMediator (factory->pMediator);
		saveCommonResource (factory);
	}

	for (index = 0; index < pathCount; index ++)
		saveString (state->pPaths [index]);

	PrynEndFunctionAfterCommon ({
		prynStringFree (&filename, state);
		prynStreamClose (stream);
	});
}

PrynExport (PrynResult, prynStateLoadAndSaveCache, (PrynState *state, const PrynString *path))
{
	PrynResult result;

	if ((result = prynStateLoadCache (state, path)) < 0) return result;
	if ((result = prynStateSaveCache (state, path)) < 0) return result;
	return PrynResultSuccess;
}

PrynExport (PrynResult, prynStateDefaultCachePath, (PrynState *state, PrynString *output))
{
	PrynStartFunctionHasState ();
	PrynString null = {0, 0};

	if (output) *output = null;
	prynAssert (state, NullArgument);

	if (output) prynCall (prynStringLiteral (output, "."));
	PrynEndFunction ();
}
